[iOS 11][ARKit] 物理衝突を実装して、キューブを落として見る #WWDC2017
1 はじめに
iOS 11で追加されたARKitを利用して、映像の中に検出した平面に、キューブを落下させている動画が、Youtubeに公開されています。
既に、ARKitで平面(水平)を検出できることは、先の記事で確かめて見ました。
今回は、物理衝突の動きを実装した四角いノードを追加して、動作を確認して見たいと思います。
本記事は Apple からベータ版として公開されているドキュメントを情報源としています。 そのため、正式版と異なる情報になる可能性があります。ご留意の上、お読みください。
2 平面上の位置をタップで指定
下記のコードでは、画面上のタップを、UITapGestureRecognizerで検出し、その座標から、hitTest(_:types:)で物理座標を取得しています。
距離判定の時は、types:にfeaturePointを指定して、アンカーに依存せず座標を取得しましたが、今回は、平面のアンカーと交差する点を取得したいので、existingPlaneUsingExtentを指定しています。
参考:[iOS 11][ARKit] 距離の計測について #WWDC2017
func addTapGesture() { let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped)) self.sceneView.addGestureRecognizer(tapRecognizer) } @objc func tapped(recognizer: UIGestureRecognizer) { let sceneView = recognizer.view as! ARSCNView let touchLocation = recognizer.location(in: sceneView) let hitTestResult = sceneView.hitTest(touchLocation, types: .existingPlaneUsingExtent) if !hitTestResult.isEmpty { if let hitResult = hitTestResult.first { addBox(hitResult :hitResult) } } }
3 キューブの生成
下記のメソッドは、hitTest(_:types:)で取得した、座標を元に、一辺が10cmのキューブを生成してシーンに追加しています。
なお、取得した平面上の座標から、Y軸方向(3次元で上下の方向)に0.2をプラスして、20cm上から落下させるようにしました。
func addBox(hitResult: ARHitTestResult) { let boxGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0) let material = SCNMaterial() material.diffuse.contents = UIImage(named: "block") boxGeometry.materials = [material] let boxNode = SCNNode(geometry: boxGeometry) // タップした位置より20cm上から落下させる boxNode.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y + 0.20, hitResult.worldTransform.columns.3.z) sceneView.scene.rootNode.addChildNode(boxNode) }
4 落下
SCNNodeには、physicsBody:SCNPhysicsBodyというプロパティがあり(デフォルトでnil)、これに物理的な形状の情報を設定すると、物理演算の対象になります。
下記のコードは、キューブノードのphysicsBodyを、元々のgeometry(形状情報)と同じもので初期化しています。
type:をdynamicにすることで、デフォルトで下方向に重力がかかり、このままでは、シーンに追加した時点で、どんどん下に落下していきます。
boxNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: boxGeometry, options: [:])) boxNode.physicsBody?.categoryBitMask = 1
落下してきたキューブが、平面で止まるようにするには、平面の方にも、物理的な形状の情報を追加する必要があります。
下記では、平面のノードに対して、キューブと同じように、geometry(形状情報)からphysicsBodyを初期化しています。なお、今度は、重力で落下しないように、type:をstatic(固定)にしています。
planeNode.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(geometry: geometry!, options: nil)) planeNode.physicsBody?.categoryBitMask = 2
これで、20cm上から落下してきたキューブは、平面でバウンドして止まります。 ただし、落下時のバウンドや、他のキューブとぶつかって、平面ノードからはみ出すと、重力により下に落下することとなり、そこが、平面ノードの検出されたエリア外である場合、見えなくなるまで奈落の底に落ちて行きます。ちょっと悲しいです。
5 最後に
今回は、物理演算を使用して、ARKitで検出した平面に、生成したノードを落下させてみました。 キューブにサイコロのテクスチャを貼れば、これだけで、机の上でサイコロが転がせそうです。
試したコードは、下記に置きました。不明な点があればご参照ください。
[GitHub] https://github.com/furuya02/ARKitPhyscsDetectionSample
6 参考リンク
Introducing ARKit
Apple Developer > Documentation > ARKit
WWDC2017 Session 602 Introducing ARKit: Augmented Reality for iOS
ARKit by Example — Part 3: Adding geometry and physics fun
[iOS 11] はじめてのARKit #WWDC2017
[iOS 11][ARKit] 距離の計測について #WWDC2017
[iOS 11][ARKit] 平面の検出について #WWDC2017